import javax.xml.bind.annotation.XmlRootElement;

import main.AtorCompilationUnit;

import codeg.CodeGeneration;
import codeg.CodeGenerationContext;
import codeg.Codeg;
import codeg.CodegException;

@XmlRootElement
public class ActorCodeg extends Codeg
{
    static class Color {
        public int red;
        public int green;
        public int blue;
        
        public Color() {}
        
        public Color(int r, int g, int b) {
            red = r;
            green = g;
            blue = b;
        }
    }
    
	private int initialX;
	private int initialY;
	private int initialAngle;
	private Color color = new Color(0, 0, 0);
	private String locyan = "";
	
    public int getInitialX() {
        return initialX;
    }
    public void setInitialX(int initialX) {
        this.initialX = initialX;
    }
    public int getInitialY() {
        return initialY;
    }
    public void setInitialY(int initialY) {
        this.initialY = initialY;
    }
    public int getInitialAngle() {
        return initialAngle;
    }
    public void setInitialAngle(int initialAngle) {
        this.initialAngle = initialAngle;
    }
    public Color getColor() {
        return color;
    }
    public void setColor(Color color) {
        this.color = color;
    }
    public String getLocyan() {
        return locyan;
    }
    public void setLocyan(String locyan) {
        this.locyan = locyan;
    }
    
    public String[] getRoles() {
        return new String[] { "locyan:actor" };
    }
    
    public Object getInformation(String id) {
        if (id == null)
            return null;
        if (id.equals("locyan:id"))
            return getId();
        return null;
    }
    public CodeGeneration getCode(CodeGenerationContext context) throws CodegException {
       CodeGeneration code = new CodeGeneration(this);
        
        code.setImports(new String[] {
        "javax.swing.JFrame",
        "java.awt.Color",
        "java.awt.Graphics",
        "java.awt.Point" });

        code.setGlobalCode(
        "/**\n"+
        " * An actor that can act inside a world.\n"+
        " */\n"+
        "class Actor extends Thread {\n"+
        "    private Point position;\n"+
        "    private double angle;\n"+
        "    private World world;\n"+
        "    private boolean penDown = true;\n"+
        "    private Color color;\n"+
        "    \n"+
        "    private final static int size = 10;\n"+
        "    \n"+
        "    /* Cache of angle properties for faster caculations */\n"+
        "    private double radAngle;\n"+
        "    private double[] cacheSinCos = new double[2];\n"+
        "\n"+
        "    /* Flag for execution control */\n"+
        "    private boolean running = false;\n"+
        "    \n"+
        "    /**\n"+
        "     * Creates an actor at the given position, rotated by angle degrees and with a color.\n"+
        "     * @param position The initial position in the world.\n"+
        "     * @param angle The initial angle of rotation.\n"+
        "     * @param color A color for graphical representation.\n"+
        "     */\n"+
        "    public Actor(Point position, double angle, Color color) {\n"+
        "        setPosition(position);\n"+
        "        setAngle(angle);\n"+
        "        setColor(color);\n"+
        "    }\n"+
        "    \n"+
        "    private int[][] getSquarePoints() {\n"+
        "        int a, b;\n"+
        "        double sin = cacheSinCos[0];\n"+
        "        double cos = cacheSinCos[1];\n"+
        "        \n"+
        "        int[] x = new int[] { position.x - size,\n"+
        "                              position.x + size,\n"+
        "                              position.x + size,\n"+
        "                              position.x - size };\n"+
        "        \n"+
        "        int[] y = new int[] { position.y + size,\n"+
        "                              position.y + size,\n"+
        "                              position.y - size,\n"+
        "                              position.y - size };\n"+
        "\n"+
        "        for (int i = 0; i < 4; i++) {\n"+
        "            a = x[i] - position.x;\n"+
        "            b = y[i] - position.y;\n"+
        "            \n"+
        "            x[i] = (int) (a*cos - b*sin) + position.x;\n"+
        "            y[i] = (int) (a*sin + b*cos) + position.y;\n"+
        "        }\n"+
        "        \n"+
        "        return new int[][] { x, y };\n"+
        "    }\n"+
        "    \n"+
        "    public void paint(Graphics g) {\n"+
        "        g.setColor(getColor());\n"+
        "        int[][] points = getSquarePoints();\n"+
        "        g.fillPolygon(points[0], points[1], 4);\n"+
        "    }\n"+
        "\n"+
        "    public void setAngle(double angle) {\n"+
        "        this.angle = angle;\n"+
        "        this.radAngle = angle * (Math.PI / 180);\n"+
        "        cacheSinCos[0] = Math.sin(radAngle);\n"+
        "        cacheSinCos[1] = Math.cos(radAngle);\n"+
        "    }\n"+
        "\n"+
        "    public double getAngle() {\n"+
        "        return angle;\n"+
        "    }\n"+
        "    \n"+
        "    public void setWorld(World world) {\n"+
        "        this.world = world;\n"+
        "    }\n"+
        "\n"+
        "    public World getWorld() {\n"+
        "        return world;\n"+
        "    }\n"+
        "\n"+
        "    public void setPosition(Point position) {\n"+
        "        this.position = new Point(position);\n"+
        "    }\n"+
        "\n"+
        "    public Point getPosition() {\n"+
        "        return new Point(position);\n"+
        "    }\n"+
        "    \n"+
        "    public void setColor(Color color) {\n"+
        "        this.color = color;\n"+
        "    }\n"+
        "\n"+
        "    public Color getColor() {\n"+
        "        return color;\n"+
        "    }\n"+
        "    \n"+
        "    public boolean isPenDown() {\n"+
        "        return penDown;\n"+
        "    }\n"+
        "    \n"+
        "    public void penUp() {\n"+
        "        penDown = false;\n"+
        "    }\n"+
        "    \n"+
        "    public void penDown() {\n"+
        "        penDown = true;\n"+
        "    }\n"+
        "    \n"+
        "    /**\n"+
        "     * Rotates the actor by angle degrees, in addition to the current rotation\n"+
        "     * angle.\n"+
        "     * The actor must be inside a world, otherwise nothing happens.\n"+
        "     * @param angle the number of degrees to rotate.\n"+
        "     */\n"+
        "    public void rotate(double angle) {\n"+
        "        if (world != null) {\n"+
        "            setAngle(this.angle + angle);\n"+
        "            world.repaint();\n"+
        "            pause();\n"+
        "        }\n"+
        "    }\n"+
        "    \n"+
        "    /**\n"+
        "     * Moves the actor by @param quantity, based on the current position and angle.\n"+
        "     * The actor must be inside a world, otherwise nothing happens.\n"+
        "     * @param quantity the quantity of movement.\n"+
        "     */\n"+
        "    public void move(int quantity) {\n"+
        "        if (world != null) {\n"+
        "            position.translate((int) (quantity*cacheSinCos[1]), \n"+
        "                               (int) (quantity*cacheSinCos[0]));\n"+
        "            this.world.registerMovement(this, getPosition(), penDown);\n"+
        "            world.repaint();\n"+
        "            pause();\n"+
        "        }\n"+
        "    }\n"+
        "    \n"+
        "    /**\n"+
        "     * Acts on the world.\n"+
        "     */\n"+
        "    public void act() {};\n"+
        "    \n"+
        "    /**\n"+
        "     * Performs the next step.\n"+
        "     */\n"+
        "    public synchronized void next() {\n"+
        "        running = true;\n"+
        "        notify();\n"+
        "    }\n"+
        "    \n"+
        "    /**\n"+
        "     * Pauses the execution.\n"+
        "     */\n"+
        "    private synchronized void pause() {\n"+
        "        synchronized (this) {\n"+
        "            running = false;\n"+
        "            try { while (!running) wait(); }\n"+
        "            catch (InterruptedException e) {}\n"+
        "        }\n"+
        "    }\n"+
        "    \n"+
        "    /**\n"+
        "     * Waits for a call to {@link Actor#next()} and start the simulation. \n"+
        "     */\n"+
        "    public void run() {\n"+
        "        pause();\n"+
        "        act();\n"+
        "    }\n"+
        "}");
    
        Color c = getColor();
        String s = "Actor " + getId() + " = new Actor(new Point(" + getInitialX() + ", " + getInitialY() + "), " + getInitialAngle() + ", new Color(" + c.red + ", " + c.green + ", " + c.blue + ")) {\n";
        AtorCompilationUnit acu = new AtorCompilationUnit();
        acu.compile(getId(), getLocyan().toCharArray());
        // TODO: stop compilation because of error
        if ( acu.getCompilationErrorList().size() != 0 )
            throw new CodegException("Error generating code for actor codeg " + getId() + ": Locyan compilation error", CodegException.UNKNOWN_ERROR);
        s += acu.getGeneratedCode().toString();
        s += "};";
        
        code.setLocalCode(s);
        
        return code;
    }
}